//-------------------------------------------------------------------
//	Copyright (c) 2010-2014  Zhirnov Andrey
//	This file is part of the "UX Standard Lib" project.
//	See copyright notice in "UX_Script.h".
//-------------------------------------------------------------------

#ifndef _UXS_SCRIPT_BINDER_H
#define _UXS_SCRIPT_BINDER_H

#pragma once

#include "ScriptEngine.h"

namespace UX_STL
{
namespace UXScript
{


	//
	// Class Binder
	//
	
	template <typename T>
	struct ClassBinder
	{
	// types
	public:
		typedef T	type;


	// variables
	private:
		Ptr< AngelScript::asIScriptEngine >		_engine;
		string									_name;
		int										_flags;


	// methods
	public:
		explicit
		ClassBinder (AngelScript::asIScriptEngine *eng);

		explicit
		ClassBinder (AngelScript::asIScriptEngine *eng, uni_c_string name);

		bool CreatePodValue (int flags = 0);
		bool CreateClassValue (int flags = 0);
		bool CreateRef (int flags = 0, bool hasFactory = true);
		bool CreateRef (T* (*create)(), void (T:: *addRef)(), void (T:: *releaseRef)(), int flags = 0);
		
		template <typename TOut>
		bool AddUnaryMathOperators ();
		
		template <typename TOut>
		bool AddUnaryBitOperators ();

		bool AddIncOperators ();

		template <typename TIn, typename TOut>
		bool AddBinaryBitOperators ();

		template <typename TIn, typename TOut>
		bool AddBinaryBitShiftOperators ();

		template <typename TIn, typename TOut>
		bool AddBinaryMathOperators ();
		
		template <typename TIn, typename TOut>
		bool AddBinaryRightBitOperators ();
		
		template <typename TIn, typename TOut>
		bool AddBinaryRightBitShiftOperators ();
		
		template <typename TIn, typename TOut>
		bool AddBinaryRightMathOperators ();
		
		template <typename TIn>
		bool AddBinaryAssignmentBitOperators ();
		
		template <typename TIn>
		bool AddBinaryAssignmentBitShiftOperators ();
		
		template <typename TIn>
		bool AddBinaryAssignmentMathOperators ();
		
		template <typename TIn, typename TOut, typename TConstOut>
		bool AddIndexOperators ();


		template <typename B>
		bool AddConstructor (B ctorPtr);

		template <typename B>
		bool AddProperty (B T::* value, uni_c_string name);

		template <typename B>
		bool AddMethod (B methodPtr, uni_c_string name);
		
		template <typename B>
		bool AddMethodFromGlobal (B funcPtr, uni_c_string description);


		string const &						Name ()	const	{ return _name; }

		Ptr< AngelScript::asIScriptEngine >	GetEngine ()	{ return _engine; }


	private:
		bool _Create (int flags);
		
		struct _Util
		{
			template <typename TIn, typename TOut>
			static string Desc (uni_c_string name)
			{
				string s;
				ScriptTypeInfo< TOut >::Name( s );		s << " " << name << "(";
				ScriptTypeInfo< TIn >::ArgName( s );	s << ") const";
				return s;
			}
		};
	};


	
/*
=================================================
	constructor
=================================================
*/
	template <typename T>
	inline ClassBinder<T>::ClassBinder (AngelScript::asIScriptEngine *eng) :
		_engine(eng), _flags(0)
	{
		ScriptTypeInfo< T >::Name( _name );
	}

/*
=================================================
	constructor
=================================================
*/
	template <typename T>
	inline ClassBinder<T>::ClassBinder (AngelScript::asIScriptEngine *eng, uni_c_string name) :
		_engine(eng), _flags(0), _name(name)
	{}

/*
=================================================
	CreatePodValue
=================================================
*/
	template <typename T>
	inline bool ClassBinder<T>::CreatePodValue (int flags)
	{
		using namespace AngelScript;

		_flags = asOBJ_VALUE | asOBJ_POD | flags;
		AS_CALL_R( _engine->RegisterObjectType( _name.cstr(), sizeof(T),  ) );
		
		CHECK_ERR( _Create( _flags ) );
		return true;
	}
	
/*
=================================================
	CreateClassValue
=================================================
*/
	template <typename T>
	inline bool ClassBinder<T>::CreateClassValue (int flags)
	{
		using namespace AngelScript;

		_flags = asOBJ_VALUE | asOBJ_APP_CLASS_CDAK | flags;
		AS_CALL_R( _engine->RegisterObjectType( _name.cstr(), sizeof(T), _flags ) );

		CHECK_ERR( _Create( _flags ) );
		return true;
	}

/*
=================================================
	CreateRef
=================================================
*/
	template <typename T>
	inline bool ClassBinder<T>::CreateRef (int flags, const bool hasFactory)
	{
		typedef T * (* constructor_t) ();

		constructor_t create = null;

		if ( hasFactory )
			create = &AngelScriptHelper::FactoryCreate<T>;
	
		return CreateRef( create, &T::__AddRef, &T::__Release, flags );
	}
	
/*
=================================================
	CreateRef
=================================================
*/
	template <typename T>
	inline bool ClassBinder<T>::CreateRef (T* (*create)(), void (T:: *addRef)(), void (T:: *releaseRef)(), int flags)
	{
		using namespace AngelScript;

		_flags = asOBJ_REF | flags | (addRef != null and releaseRef != null ? 0 : asOBJ_NOCOUNT);

		AS_CALL_R( _engine->RegisterObjectType( _name.cstr(), sizeof(T), _flags ) );
		
		if ( addRef != null )
		{
			AS_CALL_R( _engine->RegisterObjectBehaviour( _name.cstr(), asBEHAVE_ADDREF,		"void AddRef()",
							asSMethodPtr<sizeof(void (T::*)())>::Convert((void (T::*)())(addRef)),	asCALL_THISCALL ) );
		}

		if ( releaseRef != null )
		{
			AS_CALL_R( _engine->RegisterObjectBehaviour( _name.cstr(), asBEHAVE_RELEASE,	"void Release()",
							asSMethodPtr<sizeof(void (T::*)())>::Convert((void (T::*)())(releaseRef)),	asCALL_THISCALL ) );
		}

		if ( create != null )
		{
			AS_CALL_R( _engine->RegisterObjectBehaviour( _name.cstr(), asBEHAVE_FACTORY,
														(string(_name) << "@ new_" << _name << "()").cstr(),
														asFUNCTION( create ),	asCALL_CDECL ) );
		}
		/*
		AS_CALL_R( _engine->RegisterObjectMethod( _name.cstr(),
										(string(_name) << " & opAssign(const " << _name << " &in)").cstr(),
										asMETHOD( T, operator = ), asCALL_THISCALL ) );
		*/
		return true;
	}

/*
=================================================
	_Create
=================================================
*/
	template <typename T>
	inline bool ClassBinder<T>::_Create (const int flags)
	{
		using namespace AngelScript;

		// constructor
		if ( EnumCmp( flags, asOBJ_APP_CLASS_CONSTRUCTOR ) )
		{
			AS_CALL_R( _engine->RegisterObjectBehaviour( _name.cstr(), asBEHAVE_CONSTRUCT, "void f()",
											asFUNCTION( &AngelScriptHelper::Constructor<T> ), asCALL_GENERIC ) );
		}

		// destructor
		if ( EnumCmp( flags, asOBJ_APP_CLASS_DESTRUCTOR ) )
		{
			AS_CALL_R( _engine->RegisterObjectBehaviour( _name.cstr(), asBEHAVE_DESTRUCT,  "void f()",
											asFUNCTION( &AngelScriptHelper::Destructor<T> ), asCALL_GENERIC ) );
		}

		// copy constructor
		if ( EnumCmp( flags, asOBJ_APP_CLASS_COPY_CONSTRUCTOR ) )
		{
			AS_CALL_R( _engine->RegisterObjectBehaviour( _name.cstr(), asBEHAVE_CONSTRUCT,
											(string("void f(const ") << _name << " &in)").cstr(),
											asFUNCTION( &AngelScriptHelper::CopyConstructor<T> ), asCALL_GENERIC ) );
		}

		// assignment 
		if ( EnumCmp( flags, asOBJ_APP_CLASS_ASSIGNMENT ) )
		{
			AS_CALL_R( _engine->RegisterObjectMethod( _name.cstr(),
											(string(_name) << " & opAssign(const " << _name << " &in)").cstr(),
											asMETHOD( T, operator = ), asCALL_THISCALL ) );
		}
		return true;
	}
	
/*
=================================================
	AddConstructor
=================================================
*/
	template <typename T>
	template <typename B>
	inline bool ClassBinder<T>::AddConstructor (B ctorPtr)
	{
		using namespace AngelScript;

		STATIC_ASSERTE( ARGS( TypeDescriptor::IsSameTypes< void *,
			GlobalFunction<B>::args_t::type_list::Get<0>::type >::value ) );

		string	s("void f");
		GlobalFunction<B>::args_t::GetArgs<1>( s );	// skip	(void *)

		AS_CALL_R( _engine->RegisterObjectBehaviour( _name.cstr(), asBEHAVE_CONSTRUCT,
										s.cstr(), asFUNCTION( *ctorPtr ), asCALL_CDECL_OBJFIRST ) );
		return true;
	}
	
/*
=================================================
	AddProperty
=================================================
*/
	template <typename T>
	template <typename B>
	inline bool ClassBinder<T>::AddProperty (B T::* value, uni_c_string name)
	{
		string	s;
		ScriptTypeInfo<B>::Name( s );
		s << ' ' << name;

		AS_CALL_R( _engine->RegisterObjectProperty( _name.cstr(), s.cstr(), offsetof( T, *value ) ) );
		return true;
	}
	
/*
=================================================
	AddMethod
=================================================
*/
	template <typename T>
	template <typename B>
	inline bool ClassBinder<T>::AddMethod (B methodPtr, uni_c_string name)
	{
		using namespace AngelScript;

		string	s;
		MemberFunction<B>::GetDescriptor( s, name );

		AS_CALL_R( _engine->RegisterObjectMethod( _name.cstr(), s.cstr(),
							asSMethodPtr< sizeof( void (T::*)() ) >::Convert( (void (T::*)()) (methodPtr) ),
							asCALL_THISCALL ) );
		return true;
	}
	
/*
=================================================
	AddMethodFromGlobal
=================================================
*/
	template <typename T>
	template <typename B>
	inline bool ClassBinder<T>::AddMethodFromGlobal (B funcPtr, uni_c_string description)
	{
		using namespace AngelScript;

		AS_CALL_R( _engine->RegisterObjectMethod( _name.cstr(), description.cstr(), asFUNCTION( *funcPtr ), asCALL_CDECL_OBJLAST ) );
		return true;
	}

/*
=================================================
	AddUnaryBitOperators
=================================================
*/
	template <typename T>
	template <typename TOut>
	inline bool ClassBinder<T>::AddUnaryBitOperators ()
	{
		return AddMethod( static_cast< TOut (T::*) () const >(	&T::operator ~  ),	"opCom" );
	}

/*
=================================================
	AddUnaryMathOperators
=================================================
*/
	template <typename T>
	template <typename TOut>
	inline bool ClassBinder<T>::AddUnaryMathOperators ()
	{
		bool	res = true;
		res &= AddMethod( static_cast< TOut	(T::*) () const >(	&T::operator -  ),	"opNeg" );
		res &= AddMethod( static_cast< TOut	(T::*) (int) >(		&T::operator -- ),	"opPostDec" );
		res &= AddMethod( static_cast< TOut	(T::*) (int) >(		&T::operator ++ ),	"opPostInc" );
		return res;
	}
		
/*
=================================================
	AddIncOperators
=================================================
*/
	template <typename T>
	inline bool ClassBinder<T>::AddIncOperators ()
	{
		bool	res = true;
		res &= AddMethod( static_cast< T &	(T::*) ()	>(	&T::operator -- ),	"opPreDec" );
		res &= AddMethod( static_cast< T &	(T::*) ()	>(	&T::operator ++ ),	"opPreInc" );
		return res;
	}

/*
=================================================
	AddBinaryBitOperators
=================================================
*/
	template <typename T>
	template <typename TIn, typename TOut>
	inline bool ClassBinder<T>::AddBinaryBitOperators ()
	{
		bool	res = true;
		res &= AddMethod( static_cast< TOut (T::*) (TIn) const >(	&T::operator & ),	"opAnd" );
		res &= AddMethod( static_cast< TOut (T::*) (TIn) const >(	&T::operator | ),	"opOr"  );
		res &= AddMethod( static_cast< TOut (T::*) (TIn) const >(	&T::operator ^ ),	"opXor" );
		return res;
	}

/*
=================================================
	AddBinaryBitShiftOperators
=================================================
*/
	template <typename T>
	template <typename TIn, typename TOut>
	inline bool ClassBinder<T>::AddBinaryBitShiftOperators ()
	{
		bool	res = true;
		res &= AddMethod( static_cast< TOut (T::*) (TIn) const >(	&T::operator >> ),	"opShr" );
		res &= AddMethod( static_cast< TOut (T::*) (TIn) const >(	&T::operator << ),	"opShl" );
		return res;
	}

/*
=================================================
	AddBinaryMathOperators
=================================================
*/
	template <typename T>
	template <typename TIn, typename TOut>
	inline bool ClassBinder<T>::AddBinaryMathOperators ()
	{
		bool	res = true;
		res &= AddMethod( static_cast< TOut (T::*) (TIn) const >(	&T::operator + ),	"opAdd" );
		res &= AddMethod( static_cast< TOut (T::*) (TIn) const >(	&T::operator - ),	"opSub" );
		res &= AddMethod( static_cast< TOut (T::*) (TIn) const >(	&T::operator * ),	"opMul" );
		res &= AddMethod( static_cast< TOut (T::*) (TIn) const >(	&T::operator / ),	"opDiv" );
		//res &= AddMethod( static_cast< TOut (T::*) (TIn) const >(	&T::operator % ),	"opMod" );
		return res;
	}
	
/*
=================================================
	AddBinaryRightBitOperators
=================================================
*/
	template <typename T>
	template <typename TIn, typename TOut>
	inline bool ClassBinder<T>::AddBinaryRightBitOperators ()
	{
		bool	res = true;
		res &= AddMethodFromGlobal( static_cast< TOut (*) (TIn, const T&) >( &operator & ),	_Util::Desc<TIn,TOut>( "opAnd_r" ).cstr() );
		res &= AddMethodFromGlobal( static_cast< TOut (*) (TIn, const T&) >( &operator | ),	_Util::Desc<TIn,TOut>( "opOr_r"  ).cstr() );
		res &= AddMethodFromGlobal( static_cast< TOut (*) (TIn, const T&) >( &operator ^ ),	_Util::Desc<TIn,TOut>( "opXor_r" ).cstr() );
		return res;
	}
	
/*
=================================================
	AddBinaryRightBitShiftOperators
=================================================
*/
	template <typename T>
	template <typename TIn, typename TOut>
	inline bool ClassBinder<T>::AddBinaryRightBitShiftOperators ()
	{
		bool	res = true;
		res &= AddMethodFromGlobal( static_cast< TOut (*) (TIn, const T&) >( &operator >> ),	_Util::Desc<TIn,TOut>( "opShr_r" ).cstr() );
		res &= AddMethodFromGlobal( static_cast< TOut (*) (TIn, const T&) >( &operator << ),	_Util::Desc<TIn,TOut>( "opShl_r" ).cstr() );
		return res;
	}
	
/*
=================================================
	AddBinaryRightMathOperators
=================================================
*/
	template <typename T>
	template <typename TIn, typename TOut>
	inline bool ClassBinder<T>::AddBinaryRightMathOperators ()
	{
		bool	res = true;
		res &= AddMethodFromGlobal( static_cast< TOut (*) (TIn, const T&) >( &operator + ),	_Util::Desc<TIn,TOut>( "opAdd_r" ).cstr() );
		res &= AddMethodFromGlobal( static_cast< TOut (*) (TIn, const T&) >( &operator - ),	_Util::Desc<TIn,TOut>( "opSub_r" ).cstr() );
		res &= AddMethodFromGlobal( static_cast< TOut (*) (TIn, const T&) >( &operator * ),	_Util::Desc<TIn,TOut>( "opMul_r" ).cstr() );
		res &= AddMethodFromGlobal( static_cast< TOut (*) (TIn, const T&) >( &operator / ),	_Util::Desc<TIn,TOut>( "opDiv_r" ).cstr() );
		//res &= AddMethodFromGlobal( static_cast< TOut (*) (TIn, const T&) >( &::operator % ),	_Util::Desc<TIn,TOut>( "opMod_r" ).cstr() );
		return res;
	}

/*
=================================================
	AddBinaryAssignmentBitOperators
=================================================
*/
	template <typename T>
	template <typename TIn>
	inline bool ClassBinder<T>::AddBinaryAssignmentBitOperators ()
	{
		bool	res = true;
		res &= AddMethod( static_cast< T& (T::*) (TIn) >(	&T::operator &= ),	"opAndAssign" );
		res &= AddMethod( static_cast< T& (T::*) (TIn) >(	&T::operator |= ),	"opOrAssign"  );
		res &= AddMethod( static_cast< T& (T::*) (TIn) >(	&T::operator ^= ),	"opXorAssign" );
		return res;
	}

/*
=================================================
	AddBinaryAssignmentBitShiftOperators
=================================================
*/
	template <typename T>
	template <typename TIn>
	inline bool ClassBinder<T>::AddBinaryAssignmentBitShiftOperators ()
	{
		bool	res = true;
		res &= AddMethod( static_cast< T& (T::*) (TIn) >(	&T::operator >>= ),	"opShrAssign" );
		res &= AddMethod( static_cast< T& (T::*) (TIn) >(	&T::operator <<= ),	"opShlAssign" );
		return res;
	}

/*
=================================================
	AddBinaryAssignmentMathOperators
=================================================
*/
	template <typename T>
	template <typename TIn>
	inline bool ClassBinder<T>::AddBinaryAssignmentMathOperators ()
	{
		bool	res = true;
		res &= AddMethod( static_cast< T& (T::*) (TIn) >(	&T::operator += ),	"opAddAssign" );
		res &= AddMethod( static_cast< T& (T::*) (TIn) >(	&T::operator -= ),	"opSubAssign" );
		res &= AddMethod( static_cast< T& (T::*) (TIn) >(	&T::operator *= ),	"opMulAssign" );
		res &= AddMethod( static_cast< T& (T::*) (TIn) >(	&T::operator /= ),	"opDivAssign" );
		//res &= AddMethod( static_cast< T& (T::*) (TIn) >(	&T::operator %= ),	"opModAssign" );
		return res;
	}
	
/*
=================================================
	AddIndexOperators
=================================================
*/
	template <typename T>
	template <typename TIn, typename TOut, typename TConstOut>
	inline bool ClassBinder<T>::AddIndexOperators ()
	{
		bool	res = true;
		
		res &= AddMethod( static_cast< TOut      (T::*) (TIn) >(		&T::operator [] ),	"opIndex" );
		res &= AddMethod( static_cast< TConstOut (T::*) (TIn) const >(	&T::operator [] ),	"opIndex" );

		CHECK_ERR( res );
		return true;
	}
	
//-------------------------------------------------------------------

}	// UXScript
}	// UX_STL

#endif	// _UXS_SCRIPT_BINDER_H